<?php declare(strict_types=1);
/**
 * @author      xianganyall <xianganyall@gmail.com>
 * @copyright   2023-2025 owner
 **/

namespace Srv\Libs\Tools;

use Exception;
use Srv\Libs\Frame\Conf;

final class PhoneAddr
{
    private static ?self $Instance      = null;             // 实例对象
    private $fp                         = null;             // 打开的资源文件句柄
    private int $indexBegin             = 0;                // 索引开始位置
    private int $totalNum               = 0;                // 总IP数
    public const PHONE_DAT_DEFAULT_KEY  = 'PHONE.datPath';  // 默认资源文件配置Key
    public const ISP_CMCC               = 1;                // 移动
    public const ISP_CUCC               = 2;                // 联通
    public const ISP_CTCC               = 3;                // 电信
    public const ISP_CTCC_VNO           = 4;                // 电信虚拟运营商
    public const ISP_CUCC_VNO           = 5;                // 联通虚拟运营商
    public const ISP_CMCC_VNO           = 6;                // 移动虚拟运营商
    private array $ispNameList          = [
        self::ISP_CMCC      => '移动',
        self::ISP_CUCC      => '联通',
        self::ISP_CTCC      => '电信',
        self::ISP_CTCC_VNO  => '电信虚拟运营商',
        self::ISP_CUCC_VNO  => '联通虚拟运营商',
        self::ISP_CMCC_VNO  => '移动虚拟运营商',
    ];

    /**
     * @param string $fileUrl
     * @throws Exception
     * __construct
     */
    public function __construct(string $fileUrl = '')
    {
        if(strlen($fileUrl) < 1) $fileUrl = Conf::getString(self::PHONE_DAT_DEFAULT_KEY, false);
        if(($this->fp = @fopen($fileUrl, 'rb')) !== false){
            $fileSize           = filesize($fileUrl);
            fseek($this->fp, 4);
            $offset             = fread($this->fp, 4);
            $this->indexBegin   = intval(implode('', unpack('L', $offset)));
            $this->totalNum     = intval(($fileSize - $this->indexBegin)/9);
        }else{
            throw new Exception('Conf PhoneDat File Error');
        }
    }

    /**
     * __destruct
     */
    public function __destruct()
    {
        if($this->fp) @fclose($this->fp);
        $this->fp = null;
    }

    /**
     * @return static
     * instance
     */
    private static function instance():self
    {
        if(self::$Instance instanceof self) return self::$Instance;
        self::$Instance         = new self();
        return self::$Instance;
    }

    /**
     * @param string $phoneStr
     * @param bool $isFindExt
     * @return array
     * getPhoneInfo
     */
    public static function getPhoneInfo(string $phoneStr, bool $isFindExt):array
    {
        $phoneData              = [];
        if(strlen($phoneStr) !== 11) return $phoneData;
        $instance               = self::instance();
        $phonePrefix            = intval(substr($phoneStr, 0, 7));
        $phoneData              = $instance->findPhoneData($phonePrefix);
        if(!$isFindExt || count($phoneData) > 0) return $phoneData;
        $extMaxNum              = 30;
        $indexNum               = 0;
        while(++$indexNum <= $extMaxNum){
            $phoneData          = $instance->findPhoneData($phonePrefix+$indexNum);
            if(count($phoneData) > 0) return $phoneData;
            $phoneData          = $instance->findPhoneData($phonePrefix-$indexNum);
            if(count($phoneData) > 0) return $phoneData;
        }
        return $phoneData;
    }

    /**
     * @param int $phonePrefix
     * @return array
     * findPhoneData
     */
    private function findPhoneData(int $phonePrefix):array
    {
        $phoneData              = [];
        // 二分查找数据索引[由小到大]
        $left                   = 0;
        $right                  = $this->totalNum - 1;
        while($left < $right){
            $position           = $left + intval(($right - $left)/2);
            fseek($this->fp, ($position * 9) + $this->indexBegin);
            $phonePrefixPos     = intval(implode('', unpack('L', fread($this->fp, 4))));
            if($phonePrefixPos < $phonePrefix){
                $left           = $position + 1;
            }else if($phonePrefixPos > $phonePrefix){
                $right          = $position - 1;
            }else{
                fseek($this->fp, ($position * 9 + 4) + $this->indexBegin);
                $unpackPosition = unpack('L_pos/c_type', fread($this->fp, 5));
                $textPos        = intval($unpackPosition['_pos']);
                $ispType        = intval($unpackPosition['_type']);
                fseek($this->fp, $textPos);
                $text           = '';
                $chrZero        = chr(0);
                while (($tmp = fread($this->fp, 1)) !== $chrZero){
                    $text       .= $tmp;
                }
                $phoneData      = $this->parseText($text, $ispType, $phonePrefix);
                break;
            }
        }
        return $phoneData;
    }

    /**
     * @param string $text
     * @param int $ispType
     * @param int $phonePrefix
     * @return array
     * parseText
     */
    private function parseText(string $text, int $ispType, int $phonePrefix):array
    {
        $textArray      = explode('|', $text);
        return ['province' => $textArray[0]??'', 'city' => $textArray[1]??'', 'postCode' => $textArray[2]??'', 'telPrefix' => $textArray[3]??'', 'ispType' => $ispType, 'ispName' => $this->ispNameList[$ispType]??'unknown', 'prefix' => strval($phonePrefix)];
    }
}